/*
 * AnimationSample.cs
 * Author: Bruno Evangelista
 * Copyright (c) 2008 Bruno Evangelista. All rights reserved.
 *
 * THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
 * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
 * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
 * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
 * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
 * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 * 
 */
using System;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
using XNAnimation;
using XNAnimation.Controllers;
using XNAnimation.Effects;

namespace XNAnimationSample
{
    /// <summary>
    /// This is the main type for your game
    /// </summary>
    public class AnimationSample : Game
    {
        private GraphicsDeviceManager graphics;

        // Text UI
        private SpriteBatch spriteBatch;
        private SpriteFont spriteFont;
        private SpriteFont spriteFontBig;

        // Input
        private KeyboardState keyboradState;
        private KeyboardState lastKeyboradState;

        private string message = "XNA Animation System";

        // SKINNED MODEL STUFF
        #region SkinnedModel stuff

        private string[] SkinnedModelFilenames = {"Models/EnemyBeast", "Models/PlayerMarine"};

        private int activeSkinnedModel;
        private int activeAnimationClip;

        private Matrix[] finalBoneTransform;
        private SkinnedModel skinnedModel;

        private AnimationController animationController;
        private SkeletonController skeletonController;
        //private BlendController blendController;
        private float boneRotation = 0;

        private bool enableNormalTexture = false;
        private bool enableSpecularTexture = false;
        private Texture2D specularTexture;

        #endregion

        private Matrix[] cameraView;
        private Matrix cameraProjection;

        public AnimationSample()
        {
            graphics = new GraphicsDeviceManager(this);

            #if XBOX360
            graphics.PreferredBackBufferWidth = 1280;
            graphics.PreferredBackBufferHeight = 720;
            #else
            graphics.PreferredBackBufferWidth = 1024;
            graphics.PreferredBackBufferHeight = 576;
            #endif
            graphics.PreferMultiSampling = true;

            //graphics.SynchronizeWithVerticalRetrace = false;
            //IsFixedTimeStep = false;

            //IsFixedTimeStep = true;
            //TargetElapsedTime = TimeSpan.FromSeconds(1 / 30.0f);

            Content.RootDirectory = "Content";
        }

        /// <summary>
        /// Allows the game to perform any initialization it needs to before starting to run.
        /// This is where it can query for any required services and load any non-graphic
        /// related content.  Calling base.Initialize will enumerate through any components
        /// and initialize them as well.
        /// </summary>
        protected override void Initialize()
        {
            // TODO: Add your initialization logic here

            base.Initialize();
        }

        /// <summary>
        /// LoadContent will be called once per game and is the place to load
        /// all of your content.
        /// </summary>
        protected override void LoadContent()
        {
            // Create a new SpriteBatch, which can be used to draw textures.
            spriteBatch = new SpriteBatch(GraphicsDevice);
            spriteFont = Content.Load<SpriteFont>("Fonts/Arial");
            spriteFontBig = Content.Load<SpriteFont>("Fonts/Calibri");

            // Load the skinned model
            activeSkinnedModel = 1;
            skinnedModel = Content.Load<SkinnedModel>(SkinnedModelFilenames[activeSkinnedModel]);
            finalBoneTransform = new Matrix[skinnedModel.SkeletonBones.Count];

            // Create an animation controller and start a clip
            animationController = new AnimationController(skinnedModel.SkeletonBones);
            animationController.StartClip(skinnedModel.AnimationClips["Idle"]);

            specularTexture = Content.Load<Texture2D>("Textures/EnemyBeastSpecular");

            //SkeletonController skeletonController = new SkeletonController(skinnedModel.SkeletonBones);
            //Pose pose = Pose.Identity;
            //skeletonController.SetBonePose("Head", pose);

            //IAnimationInterpolatorController interpolationController;
            //interpolationController.RotationInterpolation = InterpolationMode.Linear;

            //animationController.SetCustomBoneTransform("Head", Matrix.Identity);

            cameraView = new Matrix[2];
            cameraView[0] =
                //Matrix.CreateLookAt(new Vector3(100, 130, 150), new Vector3(0, 30, 0), Vector3.Up);
                Matrix.CreateLookAt(new Vector3(0, 130, 150), new Vector3(0, 30, 0), Vector3.Up);
            cameraView[1] =
                Matrix.CreateLookAt(new Vector3(0, 15, 30), new Vector3(0, 15, 0), Vector3.Up);
            cameraProjection = Matrix.CreatePerspectiveFieldOfView(1, 1, 1, 1000);
        }

        /// <summary>
        /// UnloadContent will be called once per game and is the place to unload
        /// all content.
        /// </summary>
        protected override void UnloadContent()
        {
            // TODO: Unload any non ContentManager content here
        }

        /// <summary>
        /// Allows the game to run logic such as updating the world,
        /// checking for collisions, gathering input, and playing audio.
        /// </summary>
        /// <param name="gameTime">Provides a snapshot of timing values.</param>
        protected override void Update(GameTime gameTime)
        {
            lastKeyboradState = keyboradState;
            keyboradState = Keyboard.GetState();

            if (keyboradState.IsKeyDown(Keys.Escape))
                Exit();

            if (keyboradState.IsKeyDown(Keys.Left) &&
                lastKeyboradState.IsKeyUp(Keys.Left))
            {
                activeAnimationClip = (activeAnimationClip - 1);
                if (activeAnimationClip < 0)
                    activeAnimationClip = skinnedModel.AnimationClips.Count - 1;

                // Change the current animation clip
                //animationController.StartClip(skinnedModel.AnimationClips.Values[activeAnimationClip]);
                animationController.CrossFade(skinnedModel.AnimationClips.Values[activeAnimationClip],
                    TimeSpan.FromSeconds(0.3f));
            }
            else if (keyboradState.IsKeyDown(Keys.Right) &&
                lastKeyboradState.IsKeyUp(Keys.Right))
            {
                activeAnimationClip = (activeAnimationClip + 1) % skinnedModel.AnimationClips.Count;

                // Change the current animation clip
                //animationController.StartClip(skinnedModel.AnimationClips.Values[activeAnimationClip]);
                animationController.CrossFade(
                    skinnedModel.AnimationClips.Values[activeAnimationClip],
                    TimeSpan.FromSeconds(0.3f));
            }

            /*
            if (keyboradState.IsKeyDown(Keys.Tab) && lastKeyboradState.IsKeyUp(Keys.Tab))
            {
                activeAnimationClip = (activeAnimationClip + 1) % skinnedModel.AnimationClips.Count;
                animationController.AnimationClip = skinnedModel.AnimationClips.
                    Values[activeAnimationClip];

                animationController.Time = TimeSpan.Zero;
            }
            */

            if (keyboradState.IsKeyDown(Keys.PageUp))
            {
                message = "Custom Per-Bone Transformation";

                boneRotation += 0.02f;
            }
            else if (keyboradState.IsKeyDown(Keys.PageDown))
            {
                message = "Custom Per-Bone Transformation";

                boneRotation -= 0.02f;
            }

            if (keyboradState.IsKeyDown(Keys.F1))
            {
                message = "No Interpolation";
                animationController.TranslationInterpolation = InterpolationMode.None;
                animationController.OrientationInterpolation = InterpolationMode.None;
                animationController.ScaleInterpolation = InterpolationMode.None;
            }
            else if (keyboradState.IsKeyDown(Keys.F2))
            {
                message = "Linear Interpolation";
                animationController.TranslationInterpolation = InterpolationMode.Linear;
                animationController.OrientationInterpolation = InterpolationMode.Linear;
                animationController.ScaleInterpolation = InterpolationMode.Linear;
            }
            else if (keyboradState.IsKeyDown(Keys.F3))
            {
                message = "Cubic Interpolation";
                animationController.TranslationInterpolation = InterpolationMode.Cubic;
                animationController.OrientationInterpolation = InterpolationMode.Linear;
                animationController.ScaleInterpolation = InterpolationMode.Cubic;
            }
            else if (keyboradState.IsKeyDown(Keys.F4))
            {
                message = "Spherical Interpolation";
                animationController.TranslationInterpolation = InterpolationMode.Linear;
                animationController.OrientationInterpolation = InterpolationMode.Spherical;
                animationController.ScaleInterpolation = InterpolationMode.Linear;
            }


            if (keyboradState.IsKeyDown(Keys.D5) && lastKeyboradState.IsKeyUp(Keys.D5))
            {
                message = "Animation Looping Control";
                animationController.LoopEnabled = !animationController.LoopEnabled;
            }

            if (keyboradState.IsKeyDown(Keys.D9) && lastKeyboradState.IsKeyUp(Keys.D9))
            {
                enableNormalTexture = !enableNormalTexture;
            }
            if (keyboradState.IsKeyDown(Keys.D0) && lastKeyboradState.IsKeyUp(Keys.D0))
            {
                enableSpecularTexture = !enableSpecularTexture;
            }

            if (keyboradState.IsKeyDown(Keys.Tab) && lastKeyboradState.IsKeyUp(Keys.Tab))
            {
                message = "Forward and Backward Playback";

                if (animationController.PlaybackMode == PlaybackMode.Forward)
                    animationController.PlaybackMode = PlaybackMode.Backward;
                else
                    animationController.PlaybackMode = PlaybackMode.Forward;
            }

            if (keyboradState.IsKeyDown(Keys.Space) && lastKeyboradState.IsKeyUp(Keys.Space))
            {
                activeAnimationClip = 0;
                activeSkinnedModel = (activeSkinnedModel + 1) % SkinnedModelFilenames.Length;

                skinnedModel = Content.Load<SkinnedModel>(SkinnedModelFilenames[activeSkinnedModel]);
                animationController = new AnimationController(skinnedModel.SkeletonBones);
                animationController.StartClip(skinnedModel.AnimationClips.Values[activeAnimationClip]);
            }

            if (keyboradState.IsKeyDown(Keys.Up))
            {
                message = "Animation Speed Control";
                animationController.Speed += 0.005f;
            }
            else if (keyboradState.IsKeyDown(Keys.Down))
            {
                message = "Animation Speed Control";
                animationController.Speed -= 0.005f;
                if (animationController.Speed < 0.1f)
                    animationController.Speed = 0.1f;
            }

            // Update the animation controller
            //animationController.Update(gameTime.ElapsedGameTime, Matrix.Identity);
            rotation += (float)gameTime.ElapsedGameTime.TotalSeconds * 0.4f;

            // animationController.Update(gameTime.ElapsedGameTime, Matrix.CreateRotationY(rotation));
            animationController.Update(gameTime.ElapsedGameTime, Matrix.Identity);

            base.Update(gameTime);
        }

        private float rotation = 0;

        /// <summary>
        /// This is called when the game should draw itself.
        /// </summary>
        /// <param name="gameTime">Provides a snapshot of timing values.</param>
        protected override void Draw(GameTime gameTime)
        {
            graphics.GraphicsDevice.Clear(Color.CornflowerBlue);

            /*
            if (activeSkinnedModel == 1)
            {
                SkinnedModelBone headBone = skinnedModel.SkeletonBones["Head"];
                headBone.CustomUserTransform = Matrix.CreateRotationX(boneRotation);
            }
             */

            /*
            Matrix[] bindPose = new Matrix[skinnedModel.SkeletonBones.Count];
            Matrix[] inverseBindPose = new Matrix[skinnedModel.SkeletonBones.Count];
            skinnedModel.SkeletonBones[0].GetHierarchyBindPose(ref bindPose);
            skinnedModel.SkeletonBones[0].GetHierarchyInverseBindPoseTransfom(ref inverseBindPose);

            for (int i = 0; i < skinnedModel.SkeletonBones.Count; i++)
            {
                Matrix result = inverseBindPose[i] * bindPose[i];
                result -= Matrix.Identity;
                float EPSILON = 0.00001f;

                if (result.M11 > EPSILON || result.M12 > EPSILON || result.M13 > EPSILON ||
                    result.M14 > EPSILON || result.M21 > EPSILON || result.M22 > EPSILON ||
                    result.M23 > EPSILON || result.M24 > EPSILON || result.M31 > EPSILON ||
                    result.M32 > EPSILON || result.M33 > EPSILON || result.M34 > EPSILON ||
                    result.M41 > EPSILON || result.M42 > EPSILON || result.M43 > EPSILON ||
                    result.M44 > EPSILON)
                {
                    System.Console.WriteLine("error");
                }
            }
            */

            GraphicsDevice.RenderState.DepthBufferEnable = true;
            foreach (ModelMesh modelMesh in skinnedModel.Model.Meshes)
            {
                foreach (ModelMeshPart meshPart in modelMesh.MeshParts)
                {
                    SkinnedModelBasicEffect basicEffect = (SkinnedModelBasicEffect) meshPart.Effect;

                    basicEffect.World = Matrix.CreateRotationY(rotation);// *Matrix.CreateTranslation(5, 0, 0);
                    basicEffect.Bones = animationController.SkinnedBoneTransforms;
                    basicEffect.View = cameraView[activeSkinnedModel];
                    basicEffect.Projection = cameraProjection;

                    // OPTIONAL - Configure material
                    basicEffect.Material.DiffuseColor = new Vector3(0.8f);
                    basicEffect.Material.SpecularColor = new Vector3(0.3f);
                    basicEffect.Material.SpecularPower = 8;
                    
                    basicEffect.NormalMapEnabled = enableNormalTexture;

                    basicEffect.SpecularMapEnabled = enableSpecularTexture;
                    basicEffect.SpecularMap = specularTexture;
                    if (enableSpecularTexture)
                        basicEffect.Material.SpecularColor = new Vector3(1.0f);

                    // OPTIONAL - Configure lights
                    basicEffect.AmbientLightColor = new Vector3(0.1f);
                    basicEffect.LightEnabled = true;
                    basicEffect.EnabledLights = EnabledLights.One;
                    basicEffect.PointLights[0].Color = Vector3.One;
                    basicEffect.PointLights[0].Position = new Vector3(100, 100, 100);
                }

                
                modelMesh.Draw();
            }

            Draw2D();

            base.Draw(gameTime);
        }

        private void Draw2D()
        {
            spriteBatch.Begin();

            spriteBatch.DrawString(spriteFont,
                "Model: " + SkinnedModelFilenames[activeSkinnedModel] + "    Animation: " +
                    skinnedModel.AnimationClips.Keys[activeAnimationClip], new Vector2(10, 10),
                Color.Blue);

            spriteBatch.DrawString(spriteFont, "Speed: ", new Vector2(10, 40), Color.White);
            spriteBatch.DrawString(spriteFont, "Looping: ", new Vector2(10, 60), Color.White);
            spriteBatch.DrawString(spriteFont, "Blending: ", new Vector2(10, 90), Color.White);
            //spriteBatch.DrawString(spriteFont, "Interpolation: ", new Vector2(10, 110), Color.White);
            spriteBatch.DrawString(spriteFont, "Playing: ", new Vector2(10, 130), Color.White);

            spriteBatch.DrawString(spriteFont, "" + (int) (animationController.Speed * 1000) * 0.001f,
                new Vector2(140, 40), Color.Red);
            spriteBatch.DrawString(spriteFont, "" + animationController.LoopEnabled, new Vector2(140, 60),
                Color.Red);
            spriteBatch.DrawString(spriteFont, "None", new Vector2(140, 90), Color.Red);
            //spriteBatch.DrawString(spriteFont, "" + animationController.InterpolationMode,
            //new Vector2(140, 110), Color.Red);
            spriteBatch.DrawString(spriteFont, "" + animationController.PlaybackMode,
                new Vector2(140, 130), Color.Red);

            spriteBatch.DrawString(spriteFontBig, message, new Vector2(20, 715), Color.Yellow);
            spriteBatch.End();
        }
    }
}